package net.aspw.client.features.module.impl.combat

import net.aspw.client.Launch
import net.aspw.client.event.*
import net.aspw.client.features.module.Module
import net.aspw.client.features.module.ModuleCategory
import net.aspw.client.features.module.ModuleInfo
import net.aspw.client.utils.EntityUtils
import net.aspw.client.utils.PacketUtils
import net.aspw.client.utils.extensions.*
import net.aspw.client.utils.timer.MSTimer
import net.aspw.client.value.BoolValue
import net.minecraft.entity.Entity
import net.minecraft.entity.EntityLivingBase
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.network.Packet
import net.minecraft.network.ThreadQuickExitException
import net.minecraft.network.play.INetHandlerPlayClient
import net.minecraft.network.play.client.C02PacketUseEntity
import net.minecraft.network.play.server.*
import net.minecraft.util.AxisAlignedBB
import java.util.*
import kotlin.math.abs

@ModuleInfo(
    name = "BackTrack",
    spacedName = "Back Track",
    category = ModuleCategory.COMBAT
)
class BackTrack : Module() {
    private val resetOnVelocity = BoolValue("ResetOnVelocity", true)
    private val resetOnLagging = BoolValue("ResetOnLagging", true)

    private val storagePackets = ArrayList<Packet<INetHandlerPlayClient>>()
    private val storageEntities = ArrayList<Entity>()

    private val killAura = Launch.moduleManager.getModule(KillAura::class.java)
    private var timer = MSTimer()
    private var attacked: Entity? = null
    private val storageEntityMove = LinkedList<EntityPacketLoc>()

    private var smoothPointer = System.nanoTime()
    private var needFreeze = false

    fun onPacket(event: PacketEvent) {
        mc.thePlayer ?: return
        val packet = event.packet
        val theWorld = mc.theWorld!!
        if (packet.javaClass.name.contains("net.minecraft.network.play.server.", true)) {
            val storage = ServerPacketStorage(packet as Packet<INetHandlerPlayClient>)
            if (packet is S14PacketEntity) {
                val entity = packet.getEntity(theWorld) ?: return
                if (entity !is EntityLivingBase) return
                if (entity !is EntityPlayer) return
                entity.serverPosX += packet.func_149062_c().toInt()
                entity.serverPosY += packet.func_149061_d().toInt()
                entity.serverPosZ += packet.func_149064_e().toInt()
                val x = entity.serverPosX.toDouble() / 32.0
                val y = entity.serverPosY.toDouble() / 32.0
                val z = entity.serverPosZ.toDouble() / 32.0
                if (EntityUtils.isSelected(entity, true)) {
                    val afterBB = AxisAlignedBB(x - 0.4F, y - 0.1F, z - 0.4F, x + 0.4F, y + 1.9F, z + 0.4F)
                    val eyes = mc.thePlayer!!.getPositionEyes(1F)
                    val afterRange = getNearestPointBB(eyes, afterBB).distanceTo(eyes)
                    val beforeRange = mc.thePlayer!!.getDistanceToEntityBox(entity)

                    if (beforeRange <= 3.2) {
                        if (afterRange in 2.9..5.0 && afterRange > beforeRange + 0.02 && entity.hurtTime <= calculatedMaxHurtTime) {
                            if (!needFreeze) {
                                timer.reset()
                                needFreeze = true
                                smoothPointer = System.nanoTime()
                            }
                            if (!storageEntities.contains(entity)) storageEntities.add(entity)
                            event.cancelEvent()
                            storageEntityMove.add(EntityPacketLoc(entity, x, y, z))
                            return
                        }
                    } else {
                        if (afterRange <= beforeRange) {
                            if (needFreeze) releasePackets()
                        }
                    }
                }
                if (needFreeze) {
                    if (!storageEntities.contains(entity)) storageEntities.add(entity)
                    storageEntityMove.add(EntityPacketLoc(entity, x, y, z))
                    event.cancelEvent()
                    return
                }
                if (!event.isCancelled && !needFreeze) {
                    Launch.eventManager.callEvent(EntityMovementEvent(entity))
                    val f =
                        if (packet.func_149060_h()) (packet.func_149066_f() * 360).toFloat() / 256.0f else entity.rotationYaw
                    val f1 =
                        if (packet.func_149060_h()) (packet.func_149063_g() * 360).toFloat() / 256.0f else entity.rotationPitch
                    entity.setPositionAndRotation2(x, y, z, f, f1, 3, false)
                    entity.onGround = packet.onGround
                }
                event.cancelEvent()
            } else if (packet is S18PacketEntityTeleport) {
                val entity = theWorld.getEntityByID(packet.entityId)
                if (entity !is EntityLivingBase) return
                if (entity !is EntityPlayer) return
                entity.serverPosX = packet.x
                entity.serverPosY = packet.y
                entity.serverPosZ = packet.z
                val d0 = entity.serverPosX.toDouble() / 32.0
                val d1 = entity.serverPosY.toDouble() / 32.0
                val d2 = entity.serverPosZ.toDouble() / 32.0
                val f: Float = (packet.yaw * 360).toFloat() / 256.0f
                val f1: Float = (packet.pitch * 360).toFloat() / 256.0f
                if (!needFreeze) {
                    if (abs(entity.posX - d0) < 0.03125 && abs(entity.posY - d1) < 0.015625 && abs(entity.posZ - d2) < 0.03125) {
                        entity.setPositionAndRotation2(entity.posX, entity.posY, entity.posZ, f, f1, 3, true)
                    } else {
                        entity.setPositionAndRotation2(d0, d1, d2, f, f1, 3, true)
                    }
                    entity.onGround = packet.onGround
                } else storageEntityMove.add(EntityPacketLoc(entity, d0, d1, d2))
                event.cancelEvent()
            } else {
                if ((packet is S12PacketEntityVelocity && resetOnVelocity.get()) || (packet is S08PacketPlayerPosLook && resetOnLagging.get())) {
                    storagePackets.add(storage.packet)
                    event.cancelEvent()
                    releasePackets()
                    return
                }
                if (needFreeze && !event.isCancelled) {
                    if (packet is S19PacketEntityStatus) {
                        if (packet.opCode == 2.toByte()) return
                    }
                    storagePackets.add(storage.packet)
                    event.cancelEvent()
                }
            }
        } else if (packet is C02PacketUseEntity) {
            if (packet.action == C02PacketUseEntity.Action.ATTACK && needFreeze) {
                attacked = packet.getEntityFromWorld(theWorld)
            }
        }
    }

    @EventTarget
    fun onMotion(event: MotionEvent) {
        if (event.eventState == EventState.PRE) return
        if (needFreeze) {
            doSmoothRelease()
            if (storageEntities.isNotEmpty()) {
                var release = false // for-each
                for (entity in storageEntities) {
                    val x = entity.serverPosX.toDouble() / 32.0
                    val y = entity.serverPosY.toDouble() / 32.0
                    val z = entity.serverPosZ.toDouble() / 32.0
                    val entityBB = AxisAlignedBB(x - 0.4F, y - 0.1F, z - 0.4F, x + 0.4F, y + 1.9F, z + 0.4F)
                    var range = entityBB.getLookingTargetRange(mc.thePlayer!!)
                    if (range == Double.MAX_VALUE) {
                        val eyes = mc.thePlayer!!.getPositionEyes(1F)
                        range = getNearestPointBB(eyes, entityBB).distanceTo(eyes) + 0.075
                    }
                    if (range <= 2.9) {
                        release = true
                        break
                    }
                    val entity1 = attacked
                    if (entity1 != entity) continue
                    if (timer.hasTimePassed(100.toLong())) {
                        if (range >= 2.9) {
                            release = true
                            break
                        }
                    }
                }
                if (release) releasePackets()
            }
        }
    }

    @EventTarget
    fun onWorld(event: WorldEvent) {
        attacked = null
        storageEntities.clear()
        if (event.worldClient == null) storagePackets.clear()
    }

    private fun releasePackets() {
        attacked = null
        smoothPointer = System.nanoTime()
        val netHandler: INetHandlerPlayClient = mc.netHandler
        if (storagePackets.isEmpty()) return
        while (storagePackets.isNotEmpty()) {
            storagePackets.removeAt(0).let {
                try {
                    val packetEvent = PacketEvent(it)
                    if (!PacketUtils.packets.contains(it)) Launch.eventManager.callEvent(packetEvent)
                    if (!packetEvent.isCancelled) it.processPacket(netHandler)
                } catch (_: ThreadQuickExitException) {
                }
            }
        }
        while (storageEntities.isNotEmpty()) {
            storageEntities.removeAt(0).let { entity ->
                if (!entity.isDead) {
                    val x = entity.serverPosX.toDouble() / 32.0
                    val y = entity.serverPosY.toDouble() / 32.0
                    val z = entity.serverPosZ.toDouble() / 32.0
                    entity.setPosition(x, y, z)
                }
            }
        }
        needFreeze = false
    }

    private fun doSmoothRelease() {
        // what I wrote?
        val target = killAura!!.target
        var found = false
        var bestTimeStamp = smoothPointer.coerceAtLeast(System.nanoTime() - 200 * 1000000)
        for (it in storageEntityMove) {
            if (target == it.entity) {
                found = true
                val width = it.entity.width / 2.0
                val height = it.entity.height
                val bb =
                    AxisAlignedBB(it.x - width, it.y, it.z - width, it.x + width, it.y + height, it.z + width).expands(
                        0.1
                    )
                val range = mc.thePlayer.eyesLoc.distanceTo(bb)
                if (range < 2.9 ||
                    mc.thePlayer.getPositionEyes(3F).distanceTo(bb) < 2.8
                ) {
                    bestTimeStamp = bestTimeStamp.coerceAtLeast(it.time)
                }
            }
        }

        if (!found) releasePackets()
    }

    private val calculatedMaxHurtTime: Int
        get() = 6

    fun update() {}

    private data class ServerPacketStorage(val packet: Packet<INetHandlerPlayClient>) {
        val time = System.nanoTime()
    }

    private data class EntityPacketLoc(val entity: Entity, val x: Double, val y: Double, val z: Double) {
        val time = System.nanoTime()
    }
}